package com.gitee.l0km.beanfilter.interceptor.spring;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
import com.fasterxml.jackson.databind.deser.DeserializerCache;
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.BeanSerializer;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.gitee.l0km.beanfilter.core.CodecScope;
import com.gitee.l0km.beanfilter.core.Filterable;
import com.gitee.l0km.beanfilter.core.json.JacksonFilterableDeserializer;
import com.gitee.l0km.beanfilter.core.json.JacksonFilterableSerializer;
import com.gitee.l0km.beanfilter.interceptor.FilterContainerManager;
import com.gitee.l0km.beanfilter.interceptor.config.BeanfilterInteceptorConfiguration;
import com.google.common.base.Throwables;

/**
 * HTTP请求拦截器，实现在HTTP请求解析之前为请求方法安装字段过滤器，方法调用结束删除过滤器
 * @author guyadong
 */
@Component
public class InstallFilterInterceptor implements AsyncHandlerInterceptor,BeanfilterInteceptorConfiguration{
	private final ObjectMapper objectMapper;
	private final String contextPath;
	private final FilterContainerManager containerManager = FilterContainerManager.getInstance();
	private final SimpleModule module = new SimpleModule();

	public InstallFilterInterceptor(@Autowired ObjectMapper objectMapper, @Autowired(required = false) ServletContext servletContext) {
		this.objectMapper = objectMapper;
		/** 
		 * 当不启动WEB服务时 servletContext为null,
		 * 比如启动Spring应用时，SpringApplication.webApplicationType字段被设置为NONE:
		 * springApplication.setWebApplicationType(WebApplicationType.NONE)
		 * 此时拦截器是无效的 
		 */
		this.contextPath = null == servletContext ? null : servletContext.getContextPath();
		module.setSerializers(new SimpleSerializers());
		module.setDeserializers(new SimpleDeserializers());
		objectMapper.registerModule(module);
	}
	
	/**
	 * 判断目标是否需要拦截
	 * @param request 
	 * @param handler 
	 */
	private boolean needIntercept(HttpServletRequest request, Object handler) {
		return null != contextPath && contextPath.equals(request.getContextPath()) && (handler instanceof HandlerMethod);
	}
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		if(needIntercept(request,handler)) {
			Method method = ((HandlerMethod)handler).getMethod();
			containerManager.installIfAbsent(method, 
					this::filterableBeanSerializerOf, 
					this::filterableBeanDeserializerOf, CodecScope.JACKSON);
		}
		return true;
	}
	/**
	 * [JACKSON]返回指定Java Bean类型对应的支持Bean过滤接口({@link Filterable})的序列化器<br>
	 * 如果输入类型非Java Bean类型则返回 {@code null}
	 * @param clazz
	 */
	private Filterable filterableBeanSerializerOf(Class<?> clazz) {
		DefaultSerializerProvider def= (DefaultSerializerProvider) objectMapper.getSerializerProvider();
		DefaultSerializerProvider provider = def.createInstance(objectMapper.getSerializationConfig(), objectMapper.getSerializerFactory());
		try {
			JsonSerializer<?> serializer = provider.findTypedValueSerializer(clazz,true,null);
			if(serializer instanceof Filterable) {
				return (Filterable) serializer;
			}
			if(BeanSerializer.class.equals(serializer.getClass())) {
				/** 如果是非定制序列化器则换为 JacksonFilterableSerializer */
				JacksonFilterableSerializer filterableSerializer= new JacksonFilterableSerializer(((BeanSerializer)serializer).handledType());	
				module.addSerializer(filterableSerializer);
				def.flushCachedSerializers();
				return filterableSerializer;
			}
			return null;
		} catch (JsonMappingException e) {
			throw new RuntimeException(e);
		}
	}
	/**
	 * [JACKSON]返回指定Java Bean类型对应的支持Bean过滤接口({@link Filterable})的反序列化器<br>
	 * 如果输入类型非Java Bean类型则返回 {@code null}
	 * @param clazz
	 */
	@SuppressWarnings("unchecked")
	private Filterable filterableBeanDeserializerOf(Class<?> clazz) {
		DefaultDeserializationContext defctx = (DefaultDeserializationContext) objectMapper.getDeserializationContext();
		DefaultDeserializationContext ctxt = defctx.createInstance(objectMapper.getDeserializationConfig(), null, null);
		JavaType type = objectMapper.getTypeFactory().constructType(clazz);
		try {
			JsonDeserializer<?> deserializer= ctxt.findRootValueDeserializer(type);
			if(deserializer instanceof Filterable) {
				return (Filterable) deserializer;
			}
			if(null != deserializer && BeanDeserializer.class.equals(deserializer.getClass())) {
				/** 如果是非定制反序列化器则换为 JacksonFilterableDeserializer */
				JacksonFilterableDeserializer filterableDeserializer = new JacksonFilterableDeserializer(((BeanDeserializer)deserializer).handledType());	
				module.addDeserializer((Class<Object>)((BeanDeserializer)deserializer).handledType(), filterableDeserializer);
				InstallFilterInterceptor.clearCache(objectMapper.getDeserializationContext());
				return filterableDeserializer;
			}
			return null;

		} catch (JsonMappingException e) {
			throw new RuntimeException(e);
		}
	}
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		containerManager.clearLocal();
	}
	@Override
	public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		/**
		 * 当WEB请求为异步请求时，不会调用afterCompletion，需要实现此方法才能清理thread-local 变量
		 * 参见 org.springframework.web.servlet.DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response)方法实现代码
		 * */
		afterCompletion(request, response, handler,null);
	}
	@Override
	public void setEnableInjectFastjson(boolean enableInjectFastjson) {
		containerManager.setEnableInjectFastjson(enableInjectFastjson);
	}
	@Bean
	public BeanfilterInteceptorConfiguration beanfilterInteceptorConfiguration() {
		return this;
	}

	/**
	 * 反射方式清除{@link DeserializationContext#_cache}字段缓存
	 */
	private static void clearCache(DeserializationContext src) {
		if(null != src) {
			try {
				/** 等价于 src._cache.flushCachedDeserializers(); */
				Field field = DeserializationContext.class.getDeclaredField("_cache");
				field.setAccessible(true);
				DeserializerCache _cache = (DeserializerCache) field.get(src);
				_cache.flushCachedDeserializers();
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
	}
}
